package com.app.beu.core;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Vector;

public class NetworkCore
{
	private static NetworkCore instance = null;

	private NetworkCore()
	{
	}

	public synchronized static NetworkCore getInstance()
	{
		if (instance == null)
		{
			instance = new NetworkCore();
		}
		return instance;
	}

	// public static MoonMessageHandler networkHandler = new
	// MoonMessageHandler();
	private OutputStream outputStream;
	private InputStream inputStream;
	private Socket socket = null;
	private MessageSender sender;
	private boolean connected, connecting, connectfail;
	// private static int sendByteCount;
	public int recvByteCount;
	private int HEADER_LEN = 4;

	public boolean isConnected()
	{
		return connected;
	}

	public boolean isConnecting()
	{
		return connecting;
	}

	public void connect(String host, int port)
	{
		// System.out.println("[NETWORKUTIL] IP: " + host + " Port: " + port);
		if (connected || connecting)
		{
			return;
		}
		else
		{
			connecting = true;
			sender = new MessageSender(host, port);
			Thread sendThread = new Thread(sender);
			sendThread.start();
		}
	}

	public void sendMessage(Message message)
	{
		sender.addMessage(message);
	}

	private void close()
	{
		// System.out.println("[NETWORKUTIL] close begin");
		if (connected)
		{
			BeUMessageHandler.instance.onDisconnected();
		}
		else if (connecting)
		{
			BeUMessageHandler.instance.onConnectionFail();
		}
		connectfail = true;
		connected = false;
		connecting = false;

		if (socket != null)
		{
			try
			{
				socket.close();
			} catch (IOException e)
			{
			}
			socket = null;
		}

		if (outputStream != null)
		{
			try
			{
				outputStream.close();
			} catch (IOException e)
			{
			}
			outputStream = null;
		}
		if (inputStream != null)
		{
			try
			{
				inputStream.close();
			} catch (IOException e)
			{
			}
			inputStream = null;
		}
		System.gc();
		// System.out.println("[NETWORKUTIL] close done");
	}

	public void forceClose()
	{ // using from app layer
		// System.out.println("[NETWORKUTIL] forceClose called close network");
		if (!connectfail)
		{
			close();
		}
	}

	class MessageSender implements Runnable
	{
		private final String host;
		private final int port;
		private final Vector<Message> sendingMessage;

		public MessageSender(String host, int port)
		{
			sendingMessage = new Vector<Message>();
			this.host = host;
			this.port = port;
		}

		public void addMessage(Message message)
		{
			sendingMessage.addElement(message);
		}

		public void run()
		{
			Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
			try
			{
				doConnect();
			} catch (Exception ex)
			{
				close();
				return;
			}
			Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
			while (connected)
			{
				try
				{
					while (sendingMessage.size() > 0)
					{
						// System.out.println("[NETWORKUTIL] sendingMsg....");
						Message msg = (Message) sendingMessage.elementAt(0);
						sendingMessage.removeElementAt(0);
						byte[] msgData = msg.toByteArray();
						int msgLength = msgData.length;
						// write packet length
						for (int i = 3; i >= 0; i--)
						{
							outputStream.write(msgLength >> (i * 8));
						}
						// write out data
						outputStream.write(msgData, 0, msgData.length);
						// sendByteCount += msgLength + 4;
						outputStream.flush();
						msg.dispose();
						msg = null;
						// System.out.println("[NETWORKUTIL] sendingMsg done....");
					}
					try
					{
						Thread.sleep(100);
					} catch (InterruptedException e)
					{
						e.printStackTrace();
					}
				} catch (Exception ex)
				{
					// System.out.println("[NETWORKUTIL] =========== MessageSender "
					// + ex.toString());
					ex.printStackTrace();
					// System.out.println("[NETWORKUTIL] MessageSender called close network");
					if (!connectfail)
					{
						close();
					}
					break;
				}
			}
		}

		public void doConnect() throws Exception
		{
			System.out.println("Connecting to " + host + " via port " + port);
			try
			{
				socket = new Socket(host, port);
				outputStream = socket.getOutputStream();
				inputStream = socket.getInputStream();
			} catch (UnknownHostException e)
			{
				e.printStackTrace();
				System.err.println("Don't know about host: " + host);
				return;
			} catch (IOException e)
			{
				e.printStackTrace();
				System.err.println("Couldn't get I/O for the connection to: "
						+ host);
				return;
			}

			connected = true;
			connectfail = false;
			connecting = false;
			Thread collectorThread = new Thread(new MessageCollector());
			collectorThread.start();
			BeUMessageHandler.instance.onConnectOK();
		}
	}

	class MessageCollector implements Runnable
	{

		public void run()
		{
			byte[] message;
			while (connected)
			{
				try
				{
					message = readMessage();
					if (message != null)
					{
						try
						{
							BeUMessageHandler.instance.processMessage(message);
						} catch (Exception e)
						{
							System.out
									.println("[NETWORKUTIL] =========== MessageCollector "
											+ e.toString());
							e.printStackTrace();
						}
					}
					else
					{
						break;
					}
				} catch (IOException e1)
				{
					System.out
							.println("[NETWORKUTIL] =========== MessageCollector "
									+ e1.toString());
					e1.printStackTrace();
					System.out
							.println("[NETWORKUTIL] MessageCollector called close network");

					if (!connectfail)
					{
						close();
					}
					break;
				}
			}

		}

		private byte[] readMessage() throws IOException
		{
			// System.out.println("[NETWORKUTIL] Begin read msg");
			// read packet length

			byte[] header = new byte[HEADER_LEN];
			int offset = 0;
			int len = inputStream.read(header, 0, HEADER_LEN);
			while (len > -1 && offset < HEADER_LEN)
			{
				offset += len;
				len = inputStream.read(header, offset, HEADER_LEN - offset);
			}
			if (len == -1)
			{
				return null;
			}
			// read data buffer
			int packetLen = bytes2Int(header);
			byte[] data = new byte[packetLen];
			offset = 0;
			// System.out.println("[NETWORKUTIL]  Read msg 5");
			len = inputStream.read(data, 0, packetLen);
			// System.out.println("[NETWORKUTIL]  Read msg 6");
			while (len > -1 && offset < packetLen)
			{
				offset += len;
				len = inputStream.read(data, offset, packetLen - offset);
			}
			if (len == -1)
			{
				return null;
			}
			recvByteCount += (HEADER_LEN + packetLen);
			// System.out.println("[NETWORKUTIL]  Current packetLen = " +
			// data.length + " byte(s)");
			// System.out.println("[NETWORKUTIL]  Total recvByteCount = " +
			// recvByteCount + " byte(s)");
			return data;
		}

	}

	public int bytes2Int(byte[] data)
	{
		int res = 0;
		for (int i = 0; i < 4; i++)
		{
			res <<= 8;
			res |= (0xFF & data[i]);
		}
		return res;
	}

}
